package com.jiajia.study.nio.chapter03;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;

public class WorkerV2 implements Runnable {

    private Selector selector;

    private Thread thread;

    private String name;

    private ConcurrentLinkedQueue<Runnable> workQueue = new ConcurrentLinkedQueue<>();

    private volatile Boolean isCreated = false;
    public WorkerV2(String name) {
        this.name = name;
    }

    public void register(SocketChannel socketChannel) throws IOException {
        if(!isCreated){
            selector = Selector.open();
            thread = new Thread(this,name);
            thread.start();
            isCreated = true;
        }

        workQueue.add(()->{
            try {
                socketChannel.register(selector,SelectionKey.OP_READ);
            } catch (ClosedChannelException e) {
                throw new RuntimeException(e);
            }
        });
        /**
         * 这里和下面的的 select 顺序问题会造成卡住
         *
         * 先执行了 selector.select() 方法，这时 selector 就阻塞住了，没有办法完成 register
         * 可以通过 wakeup 打断 selector 的阻塞，但是下一轮阻塞依然有可能在 register 之前执行
         *
         * 原因：
         *      register 和 select 在两个完成中，无法保证执行顺序
         *
         * 解决方案：
         *      1. 加锁
         *      2. 将 register 的操作放入到 select 的线程中
         *
         *
         */
//        socketChannel.register(selector, SelectionKey.OP_READ);
        selector.wakeup();
    }

    @Override
    public void run() {
        while(true){
            try {
                /**
                 * 阻塞等待
                 *
                 * 执行分析：
                 *
                 * 情况一 ：
                 *      1. 执行，start 后，selector.select 阻塞住
                 *      2. 提交了 runnable
                 *      3. selector.wakeup 执行
                 *      4. 拿到队列中 runnable ,完成注册，下一轮就可以成功监听
                 *
                 * 情况二：
                 *      1. 执行 start 后，还没有开始执行 selector.select
                 *      2. 执行了 提交 runnable
                 *      3. 执行了 wakeup
                 *      4. 这时 selector.select 执行了，但是已经被 wakeup 了，这里不会阻塞
                 *      5. 从队列中拿到 runnable,完成注册，下一轮成功监听
                 */
                selector.select();
                // 拿到需要执行的任务，完成注册，这样  select 和 register 就在一个线程中了
                Runnable poll = workQueue.poll();
                if(poll != null){
                    poll.run();
                }
                System.out.println("thread : " + Thread.currentThread().getName() + "完成了注册");
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while(iterator.hasNext()){
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    if(selectionKey.isReadable()){
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                        int read = socketChannel.read(byteBuffer);
                        if(read == -1){
                            selectionKey.cancel();
                            socketChannel.close();
                        }
                        byteBuffer.flip();
                        System.out.println("Thread : " + Thread.currentThread().getName() +",data is :" + StandardCharsets.UTF_8.decode(byteBuffer));
                        byteBuffer.compact();
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
